package com.imgo.max.experience.service;

import com.google.common.cache.*;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.imgo.max.cache.guava.ThreadPool;
import com.imgo.max.cache.util.CacheCommonManager;
import com.imgo.max.common.dto.ResultDO;
import com.imgo.max.common.log.CustomLogger;
import com.imgo.max.common.log.CustomLoggerFactory;
import com.imgo.max.common.result.ModuleResultCode;
import com.imgo.max.common.util.LoggerUtils;
import com.imgo.max.experience.dao.UserExperienceMapper;
import com.imgo.max.experience.dto.UserExperienceDto;
import com.imgo.max.experience.mapping.UserExperience;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkArgument;

/**
 * @Auth peter.Y
 * @Date 2016/07/27.
 */
public abstract class ExperienceHandlerParent {

    private static final CustomLogger logger = CustomLoggerFactory.getLogger(GeneralExperienceServiceImpl.class);

    @Autowired
    CacheCommonManager cacheCommonManager;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //注意：此对象不自动注入，而是由子类的getExprMapper()方法来实例化
    UserExperienceMapper experienceMapper;

    protected HashOperations<String, String, String> hashRedis;

    private String redis_key = "";

    protected abstract UserExperienceMapper getExprMapper();

    protected abstract String getRedisKey();

    protected abstract UserExperienceDto newDtoInstance();

    protected abstract void initExprMappingList();

    protected abstract Integer getLevel(Integer expr);

    protected abstract String getRole();

    @PostConstruct
    public void init() {
        hashRedis = stringRedisTemplate.opsForHash();
        experienceMapper = getExprMapper();
        redis_key = getRedisKey();
        initExprMappingList();
    }

    /**
     * 批量获取用户经验
     *
     * @param uids
     * @return
     */
    public ResultDO<List<UserExperienceDto>> getUsersExperience(List<String> uids) {
        ResultDO<List<UserExperienceDto>> resultDO = ResultDO.buildResult();
        try {
            checkArgument(CollectionUtils.isNotEmpty(uids), "用户ID列表不能为空");

            List<UserExperienceDto> exprDtos = new ArrayList<UserExperienceDto>();

            //-------------------------1、从redis缓存查找------------------------------------
            List<String> redisExprs = getRedisDatas(uids);
            List<String> notFoundIdInRedis = new ArrayList<String>();
            int i = 0, j = 0;
            for (String uid : uids) {
                //redis查询的key和value的顺序是一致的，所以是一一对应的
                String exprStr = redisExprs.get(i);

                if (null == exprStr) {
                    notFoundIdInRedis.add(uid);
                    j++;  //需要查询数据库的id数量，不是结果数量
                } else {
                    UserExperienceDto exprDto = newDtoInstance();
                    exprDto.setUid(uid);
                    exprDto.setExperience(Integer.valueOf(exprStr));
                    exprDto.setRole(getRole());//普通用户, 练习生中的查询方法会覆盖此角色
                    exprDto.setLevel(getLevel(Integer.valueOf(exprStr)));
                    exprDtos.add(exprDto);
                }
                i++;
            }

            //-------------------------2、从数据库查找,然后加入缓存在------------------------------------
            if (j > 0) {
                List<UserExperience> exprList = getDbDatas(notFoundIdInRedis);

                Map<String, String> toRedisMap = new HashMap();
                for (UserExperience expr : exprList) {
                    int exprInt = getExperience(expr);
                    UserExperienceDto exprDto = newDtoInstance();
                    exprDto.setUid(expr.getUid());
                    exprDto.setExperience(exprInt);
                    exprDtos.add(exprDto);
                    exprDto.setRole(getRole());//普通用户, 练习生中的查询方法会覆盖此角色
                    exprDto.setLevel(getLevel(exprInt));
                    toRedisMap.put(expr.getUid(), String.valueOf(exprInt));
                }

                //加入到redis缓存
                putExprToRedis(toRedisMap);
            }
            resultDO.setModule(exprDtos);
        } catch (Exception e) {
            LoggerUtils.errorAndResultDO(logger, e, ModuleResultCode.EXPERIENCE, resultDO, uids);
        }
        return resultDO;
    }


    /**
     * 获取用户经验
     *
     * @param uid 用户ID
     * @return
     */
    public ResultDO<UserExperienceDto> getUserExperience(String uid) {
        ResultDO<UserExperienceDto> resultDO = ResultDO.buildResult();
        try {
            checkArgument(StringUtils.isNotBlank(uid), "用户ID不能为空");

            Integer expr = getRedisData(uid);
            if (null == expr) { // Redis中不存在，查询数据库
                UserExperience exprDo = getDbData(uid);
                expr = getExperience(exprDo);
                // 数据保存到Redis中
                setRedisData(uid, expr);
            }
            UserExperienceDto exprDto = newDtoInstance();
            exprDto.setUid(uid);
            exprDto.setRole(getRole());//普通用户, 练习生中的查询方法会覆盖此角色
            exprDto.setExperience(expr);
            exprDto.setLevel(getLevel(expr));
            resultDO.setModule(exprDto);
        } catch (Exception e) {
            LoggerUtils.errorAndResultDO(logger, e, ModuleResultCode.EXPERIENCE, resultDO, uid);
        }
        return resultDO;
    }

    private int getExperience(UserExperience exprDo) {
        int exp = 0;
        if (exprDo != null && exprDo.getExperience() != null) {
            exp = exprDo.getExperience().intValue();
        }
        return exp;
    }

    //用户等级的缓存
    LoadingCache<String, Integer> levelLocalCache = CacheBuilder.newBuilder()
            // 设置并发级别为8，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(10)
            // 设置写缓存后8秒钟过期
            .expireAfterWrite(1, TimeUnit.DAYS)
            // 设置缓存多久失效,异步刷新
            .refreshAfterWrite(30, TimeUnit.SECONDS)
            // 设置缓存容器的初始容量为10
            .initialCapacity(1000)
            // 设置缓存最大容量为100，超过100之后就会按照LRU最近虽少使用算法来移除缓存项
            .maximumSize(10000)
            // 设置要统计缓存的命中率
            .recordStats()
            .build(new CacheLoader<String, Integer>() {

                /*
                 * (non-Javadoc)
                 * <p>Description: 加载:同步加载</p>
                 * @see com.google.common.cache.CacheLoader#load(java.lang.Object)
                 */
                @Override
                public Integer load(String key) throws InterruptedException, ExecutionException {
                    try {
                        return refreshLocalCache(key);
                    } catch (Exception e) {
                        logger.error("加载数据失败. req key:{}, error : {}", key, e);
                        throw e;
                    }
                }

                /*
                 * (non-Javadoc)
                 * <p>Description: 刷新:异步加载</p>
                 * @see com.google.common.cache.CacheLoader#reload(java.lang.Object, java.lang.Object)
                 */
                @Override
                public ListenableFuture<Integer> reload(final String key, Integer oldValue) throws InterruptedException,
                        ExecutionException {
                    ListenableFutureTask<Integer> task = ListenableFutureTask.create(new Callable<Integer>() {
                        public Integer call() throws InterruptedException, ExecutionException {
                            try {
                                return refreshLocalCache(key);
                            } catch (Exception e) {
                                logger.error("异步刷新数据失败. req key:{}, error : {}", key, e);
                                throw e;
                            }
                        }
                    });
                    ThreadPool.execute(task);
                    return task;
                }
            });

    private Integer refreshLocalCache(String key) {
        ResultDO<UserExperienceDto> resultDO = getUserExperience(key);
        if (ResultDO.isSuccess(resultDO)) {
            Integer level = 0;
            if (resultDO.getModule() != null) {
                level = resultDO.getModule().getLevel();
            }
            return level;
        }
        return null;
    }

    /**
     * 批量查询用户等级
     *
     * @param uids
     * @return
     */
    public ResultDO<Map<String, Integer>> getUsersLevel(List<String> uids) {
        ResultDO<Map<String, Integer>> resultDO = ResultDO.buildResult();
        try {
            //从本地缓存取
            Map<String, Integer> result = levelLocalCache.getAll(uids);
            resultDO.setModule(result);
        } catch (ExecutionException e) {
            LoggerUtils.errorAndResultDO(logger, e, ModuleResultCode.EXPERIENCE, resultDO, uids);
        }
        return resultDO;
    }

    /**
     * 获取用户等级
     *
     * @param uid
     * @return
     */
    public ResultDO<Integer> getUserLevel(String uid) {
        ResultDO<Integer> resultDO = ResultDO.buildResult();
        try {
            Integer level = levelLocalCache.get(uid);
            if (level == null) {
                level = 0;
            }
            resultDO.setModule(level);
        } catch (ExecutionException e) {
            LoggerUtils.errorAndResultDO(logger, e, ModuleResultCode.EXPERIENCE, resultDO, uid);
        }
        return resultDO;
    }

    /**
     * 刷新梦想基金的缓存
     *
     * @param uid
     */
    protected int freshExperience(String uid) {
        UserExperience exprDo = experienceMapper.queryExperienceByUid(uid);
        int expr = exprDo.getExperience();
        setRedisData(uid, expr);
        return expr;
    }


    protected int addDbExperience(String uid, Integer expr) {
        UserExperience praExpr = new UserExperience();
        praExpr.setUid(uid);
        praExpr.setExperience(expr);
        int result = experienceMapper.increaseExperience(praExpr);
        return result;
    }

    /**
     * 增加缓存中的经验
     *
     * @param uid
     * @param expr
     */
    protected void setRedisData(String uid, Integer expr) {
        if (expr != null) {
            try {
                cacheCommonManager.hashSet(redis_key, uid, expr.toString(), CacheCommonManager.RedisTemplateEnum.STRINGSERIALIZER);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }


    protected Integer getRedisData(String uid) {
        String expr = null;
        try {
            //注意：redis_key由子类来实例化
            expr = cacheCommonManager.hashGet(redis_key, uid, CacheCommonManager.RedisTemplateEnum.STRINGSERIALIZER);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null == expr ? null : Integer.valueOf(expr);
    }

    protected List<String> getRedisDatas(List<String> uids) {
        List<String> exprs = null;
        try {
            exprs = cacheCommonManager.hashMultiGet(redis_key, uids, CacheCommonManager.RedisTemplateEnum.STRINGSERIALIZER);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return exprs;
    }


    protected void putExprToRedis(Map<String, String> exprs) {
        if (exprs != null && exprs.size() > 0) {
            try {
                cacheCommonManager.hashPutAll(redis_key, exprs, CacheCommonManager.RedisTemplateEnum.STRINGSERIALIZER);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }


    protected UserExperience getDbData(String uid) {
        UserExperience expr = experienceMapper.queryExperienceByUid(uid);
        return expr;
    }

    protected List<UserExperience> getDbDatas(List<String> uids) {
        List<UserExperience> exprs = experienceMapper.queryExperienceByUids(uids);
        return exprs;
    }

}
